package instancecreate

import (
	"errors"
	"gitee.com/laofa1/play/k_conf_mng/kconf_backend/models"
	"gitee.com/laofa1/play/k_conf_mng/kconf_backend/pkg/instancecreate/jenkins"
	"gitee.com/laofa1/play/k_conf_mng/kconf_backend/pkg/setting"
	"log"
	"strconv"
	"strings"
	"sync"
)

func DeleteInstance(envId string) error {
	instance, err := models.GetEnvInstance(envId, "ID")
	if err != nil {
		log.Println(err)
		return err
	}

	// delete jenkins jobs
	var wg sync.WaitGroup
	// be careful with the number of errChannel, it should equal to used number
	errChannels := make([]chan string, 8)
	for i := 0; i < 8; i++ {
		errChannels[i] = make(chan string, 1)
	}

	for i, eachJob := range []string{
		instance.DBJobName,
		instance.BackendJobName,
		instance.CustomerJobName,
		instance.AdminJobName,
		instance.ApplicationRestartJobName,
	} {
		wg.Add(1)
		go func(jobName string, errChannel chan string){
			defer wg.Done()
			defer close(errChannel)
			if err := jenkins.DeleteJob(jobName); err != nil {
				errChannel <- err.Error()
			}
		}(eachJob, errChannels[i])
	}

	wg.Add(1)
	go func(errChannel chan string){
		defer wg.Done()
		defer close(errChannel)
		if err := jenkins.DeleteView(instance.Name); err != nil {
			errChannel <- err.Error()
		}
	}(errChannels[5])

	// delete docker service and network
	wg.Add(1)
	go func(errChannel1, errChannel2 chan string){
		defer wg.Done()
		defer close(errChannel1)
		defer close(errChannel2)
		if err := DeleteDockerServices(
				[]string{
					instance.CeleryWorkerServiceName,
					instance.CeleryBeatServiceName,
					instance.BackendServiceName,
					instance.CustomerServiceName,
					instance.AdminServiceName,
					instance.DBServiceName,
					instance.RedisServiceName,
				}); err != nil {
			errChannel1 <- err.Error()
		}
		if err := DeleteNetwork(instance.DockerNetwork); err != nil {
			errChannel1 <- err.Error()
		}
	}(errChannels[6], errChannels[7])

	wg.Wait()
	errMark := false
	for _, eachChannel := range errChannels {
		if errMsg := <-eachChannel; errMsg != "" {
			log.Println(errMsg)
			errMark = true
		}
	}
	if errMark {
		errMsg := "errors in delete env from jenkins and docker"
		log.Println(errMsg)
		return errors.New(errMsg)
	}


	// delete env instance
	err = models.DeleteEnv(instance.ID)
	if err != nil {
		log.Println(err)
		return err
	}
	// delete used ports
	ports := []models.EnvUsedPort{
		{Port: instance.DBPort},
		{Port: instance.RedisPort},
		{Port: instance.CustomerPort},
		{Port: instance.AdminPort},
		{Port: instance.MobilePort},
	}
	err = models.DeleteUsedPorts(ports)
	if err != nil {
		log.Println(err)
		return err
	}

	return nil
}

func CreateInstance(envName string, user string) error {
	// get all used ports, and select available ports
	allUsedPorts, err := models.GetAllUsedPorts()
	if err != nil {
		return err
	}

	var errMsg string
	// get port for customer port
	var customerPort int
	for port := setting.InstanceCustomerPortRangeLow; port <= setting.InstanceCustomerPortRangeHigh; port++ {
		isFound := 0
		customerPort = port
		if len(allUsedPorts) == 0 {
			break
		}
		for _, oldPort := range allUsedPorts {
			if port == oldPort.Port {
				isFound = 1
				break
			}
		}
		if isFound == 0 {
			break
		}
	}
	if customerPort == setting.InstanceCustomerPortRangeHigh {
		errMsg = "customer ports have used up"
		log.Println(errMsg)
		return errors.New(envName)
	}

	// get admin portal port
	var adminPort int
	for port := setting.InstanceAdminPortRangeLow; port <= setting.InstanceAdminPortRangeHigh; port++ {
		isFound := 0
		adminPort = port
		if len(allUsedPorts) == 0 {
			break
		}
		for _, oldPort := range allUsedPorts {
			if port == oldPort.Port {
				isFound = 1
				break
			}
		}
		if isFound == 0 {
			break
		}
	}
	if adminPort == setting.InstanceAdminPortRangeHigh {
		errMsg = "admin ports have used up"
		log.Println(errMsg)
		return errors.New(errMsg)
	}

	// get mobile port
	var mobilePort int
	for port := setting.InstanceMobilePortRangeLow; port <= setting.InstanceMobilePortRangeHigh; port++ {
		mobilePort = port
		isFound := 0
		if len(allUsedPorts) == 0 {
			break
		}
		for _, oldPort := range allUsedPorts {
			if port == oldPort.Port {
				isFound = 1
				break
			}
		}
		if isFound == 0 {
			break
		}
	}
	if mobilePort == setting.InstanceMobilePortRangeHigh {
		errMsg = "mobile ports have used up"
		log.Println(errMsg)
		return errors.New(errMsg)
	}

	// get db port
	var dbPort int
	for port := setting.InstanceDBPortRangeLow; port <= setting.InstanceDBPortRangeHigh; port++ {
		dbPort = port
		isFound := 0
		if len(allUsedPorts) == 0 {
			break
		}
		for _, oldPort := range allUsedPorts {
			if port == oldPort.Port {
				isFound = 1
				break
			}
		}
		if isFound == 0 {
			break
		}
	}
	if dbPort == setting.InstanceDBPortRangeHigh {
		errMsg = "db ports have used up"
		log.Println(errMsg)
		return errors.New(errMsg)
	}

	// get redis port
	var redisPort int
	for port := setting.InstanceRedisPortRangeLow; port <= setting.InstanceRedisPortRangeHigh; port++ {
		redisPort = port
		isFound := 0
		if len(allUsedPorts) == 0 {
			break
		}
		for _, oldPort := range allUsedPorts {
			if port == oldPort.Port {
				isFound = 1
				break
			}
		}
		if isFound == 0 {
			break
		}
	}
	if redisPort == setting.InstanceRedisPortRangeHigh {
		errMsg = "redis ports have used up"
		log.Println(errMsg)
		return errors.New(errMsg)
	}

	envInstance := models.EnvInstance{
		Name:      envName,
		CreatedBy: user,

		MainDomain: setting.InstanceDomainName,
		AccessIP: setting.InstanceAccessIP,

		CustomerSubDomain:  envName,
		CustomerPort:       customerPort,
		CustomerGit:        setting.InstanceCustomerGit,
		CustomerJobName: strings.ToUpper(envName) + setting.CustomerJobSuffix,
		CustomerServiceName: envName + strings.ToLower(setting.CustomerJobSuffix),
		CustomerDocVolume:  setting.DockerVolumeRoot + "/" + envName + "/customer_doc",
		CustomerDockerLabel: "",

		BackendGit:        setting.InstanceBackendGit,
		BackendJobName: strings.ToUpper(envName) + setting.BackendJobSuffix,
		BackendServiceName:  envName + strings.ToLower(setting.BackendJobSuffix),
		CeleryBeatServiceName:  envName + strings.ToLower(setting.CeleryJobSuffix) + "_beat",
		CeleryWorkerServiceName:  envName + strings.ToLower(setting.CeleryJobSuffix) + "_worker",
		CeleryJobName: strings.ToUpper(envName) + setting.CeleryJobSuffix,
		BackendStaticVolume: setting.DockerVolumeRoot + "/" + envName + "/backend_collectstatic",
		BackendMediaVolume:  setting.DockerVolumeRoot + "/backend_media_share",
		BackendDockerLabel: "",

		DockerNetwork:           envName + strings.ToLower(setting.InstanceNetworkSuffix),

		AdminSubDomain:  "admin." + envName,
		AdminPort:       adminPort,
		AdminJobName: strings.ToUpper(envName) + setting.AdminJobSuffix,
		AdminServiceName: envName + strings.ToLower(setting.AdminJobSuffix),
		AdminGit: setting.InstanceAdminGit,
		AdminDocVolume:  setting.DockerVolumeRoot + "/" + envName + "/admin_doc",
		AdminDockerLabel: "",

		MobileSubDomain:  "m." + envName,
		MobilePort:       mobilePort,
		MobileJobName: strings.ToUpper(envName) + setting.MobileJobSuffix,
		MobileServiceName: envName + strings.ToLower(setting.MobileJobSuffix),
		MobileGit: setting.InstanceMobileGit,
		MobileDocVolume:  setting.DockerVolumeRoot + "/" + envName + "/mobile_doc",
		MobileDockerLabel: "",

		DBName:     setting.InstanceDBName,
		DBUser:     setting.InstanceDBUser,
		DBServiceName: envName + strings.ToLower(setting.DBJobSuffix),
		DBJobName: strings.ToUpper(envName) + setting.DBJobSuffix,
		DBPort: dbPort,
		DBDockerLabel: setting.InstanceDBKhost,

		RedisServiceName: envName + strings.ToLower(setting.RedisJobSuffix),
		RedisJobName: strings.ToUpper(envName) + setting.RedisJobSuffix,
		RedisPort: redisPort,
		RedisDockerLabel: setting.InstanceRedisKhost,

		ApplicationRestartJobName: strings.ToUpper(envName) + setting.ApplicationRestartJobSuffix,
	}

	// write information into db
	_, err = models.CreateEnvInstance(&envInstance)
	if err != nil {
		return err
	}
	// write ports into db
	ports := []models.EnvUsedPort{
		{Port: customerPort},
		{Port: adminPort},
		{Port: dbPort},
		{Port: redisPort},
	}
	err = models.UpdateUsedPorts(ports)
	if err != nil {
		log.Println(err)
		return err
	}

	// create docker network
	err = CreateNetwork(envInstance.DockerNetwork)
	if err != nil {
		log.Println(err)
		return err
	}

	// create customer portal and admin port nginx config file and apply certs from lets's encrypt
	go func() {
		// write db to mark starting of creating of nginx
		err := models.WriteCreateState(envName, "begin", "NginxCreateState")
		if err != nil {
			log.Println(err)
			return
		}

		nginxTemplateParam := NginxTemplateParam{
			MainDomain: envInstance.MainDomain,
			CustomerSubDomain: envInstance.CustomerSubDomain,
			AdminSubDomain: envInstance.AdminSubDomain,
			MobileSubDomain: envInstance.MobileSubDomain,
			CustomerPort: envInstance.CustomerPort,
			AdminPort: envInstance.AdminPort,
			MobilePort: envInstance.MobilePort,
			IP: setting.InstanceAccessIP,
		}
		err = GenerateNginx(nginxTemplateParam)
		if err != nil {
			log.Println(err)
			err = models.WriteCreateState(envName, err.Error(), "NginxCreateState")
			if err != nil {
				log.Println(err)
				return
			}
			return
		}
		// write result to db
		err = models.WriteCreateState(envName, "ok", "NginxCreateState")
		if err != nil {
			log.Println(err)
		}
	}()

	// create db
	go func() {
		// write db
		dbParam := DbCreateParam{
			ServiceName:    envInstance.DBServiceName,
			ServiceNetwork: envInstance.DockerNetwork,
			DockerLabel:          envInstance.DBDockerLabel,
			OutPort:        strconv.Itoa(envInstance.DBPort),
			Password:       setting.InstanceDBPassword,
			UserName:       envInstance.DBUser,
		}
		// write state in db
		err := models.WriteCreateState(envName, "begin", "DBCreateState")
		if err != nil {
			log.Println(err)
			return
		}

		err = CreateDb(dbParam)
		if err != nil {
			log.Println(err)
			err = models.WriteCreateState(envName, err.Error(), "DBCreateState")
			if err != nil {
				log.Println(err)
				return
			}
			return
		}
		// update state
		err = models.WriteCreateState(envName, "ok", "DBCreateState")
		if err != nil {
			log.Println(err)
			return
		}
	}()

	// create redis
	go func() {
		redisParam := RedisCreateParam{
			ServiceName:    envInstance.RedisServiceName,
			ServiceNetwork: envInstance.DockerNetwork,
			OutPort:        strconv.Itoa(envInstance.RedisPort),
			DockerLabel:          envInstance.RedisDockerLabel,
		}
		// write state in db
		err := models.WriteCreateState(envName, "begin", "RedisCreateState")
		if err != nil {
			log.Println(err)
			return
		}

		err = CreateRedis(redisParam)
		if err != nil {
			log.Println(err)
			err = models.WriteCreateState(envName, err.Error(), "RedisCreateState")
			if err != nil {
				log.Println(err)
				return
			}
			return
		}
		// update state
		err = models.WriteCreateState(envName, "ok", "RedisCreateState")
		if err != nil {
			log.Println(err)
			return
		}
	}()

	// create jenkins jobs
	go func() {
		// write db
		err := models.WriteCreateState(envName, "begin", "JenkinsCreateState")
		if err != nil {
			log.Println(err)
			return
		}

		customerJobCreateParam := jenkins.CustomerJobCreateParam{
			EnvName: envInstance.Name,
			ServiceName: envInstance.CustomerServiceName,
			JobName: envInstance.CustomerJobName,
			DockerLabel: "",
			MainDomain: envInstance.MainDomain,
			SubDomain: envInstance.CustomerSubDomain,
			Git: envInstance.CustomerGit,
			Port: envInstance.CustomerPort,
			DocVolume: envInstance.CustomerDocVolume,
			BackendServiceName: envInstance.BackendServiceName,
			BackendStaticVolume: envInstance.BackendStaticVolume,
			BackendPubMediaVolume: envInstance.BackendMediaVolume + "/public_media",
			DockerNetwork: envInstance.DockerNetwork,
			DockerRepository: setting.DockerRepo,
			DockerManagerHost: setting.DockerManagerHost,
			DockerManagerUser: setting.DockerManagerUser,
			DockerManagerPort: setting.DockerManagerPort,
			DockerBuildResourceRoot: setting.DockerBuildResourceRoot,

			JobScriptTemplateFile: setting.JenkinsCustomerScriptTemplate,
			JobConfigTemplateFile: setting.JenkinsCustomerJobTemplate,
		}

		adminJobCreateParam := jenkins.AdminJobCreateParam{
			EnvName: envInstance.Name,
			ServiceName: envInstance.AdminServiceName,
			JobName: envInstance.AdminJobName,
			DockerLabel: "",
			MainDomain: envInstance.MainDomain,
			SubDomain: envInstance.AdminSubDomain,
			Git: envInstance.AdminGit,
			Port: envInstance.AdminPort,
			DocVolume: envInstance.AdminDocVolume,
			BackendServiceName: envInstance.BackendServiceName,
			DockerNetwork: envInstance.DockerNetwork,
			DockerRepository: setting.DockerRepo,
			DockerManagerHost: setting.DockerManagerHost,
			DockerManagerUser: setting.DockerManagerUser,
			DockerManagerPort: setting.DockerManagerPort,
			DockerBuildResourceRoot: setting.DockerBuildResourceRoot,

			JobScriptTemplateFile: setting.JenkinsAdminScriptTemplate,
			JobConfigTemplateFile: setting.JenkinsAdminJobTemplate,
		}

		// mobileJobCreateParam := MobileJobCreateParam{
		// 	EnvName: envInstance.Name,
		// 	KHost: "",
		// 	ServiceName: envInstance.MobileServiceName,
		// 	JobName: envInstance.MobileJobName,
		// 	DockerLabel: "",
		// 	MainDomain: envInstance.MainDomain,
		// 	SubDomain: envInstance.MobileSubDomain,
		// 	Git: envInstance.MobileGit,
		// 	Port: envInstance.MobilePort,
		// 	DocVolume: envInstance.MobileDocVolume,
		// 	BackendServiceName: envInstance.BackendServiceName,
		// 	DockerNetwork: envInstance.DockerNetwork,
		// 	DockerRepository: setting.DockerRepo,
		// 	DockerManagerHost: setting.DockerManagerHost,
		// 	DockerManagerUser: setting.DockerManagerUser,
		// 	DockerManagerPort: setting.DockerManagerPort,
		// 	DockerBuildResourceRoot: setting.DockerBuildResourceRoot,
		// }

		dBJobCreateParam := jenkins.DBJobCreateParam{
			EnvName: envInstance.Name,
			JobName: envInstance.DBJobName,
			Port: envInstance.DBPort,
			DockerManagerHost: setting.DockerManagerHost,
			DockerManagerUser: setting.DockerManagerUser,
			DockerManagerPort: setting.DockerManagerPort,
			BackendServiceName: envInstance.BackendServiceName,
			CeleryWorkerServiceName: envInstance.CeleryWorkerServiceName,
			CeleryBeatServiceName: envInstance.CeleryBeatServiceName,
			JobScriptTemplateFile: setting.JenkinsDBScriptTemplate,
			JobConfigTemplateFile: setting.JenkinsDBJobTemplate,
		}

		backendJobCreateParam := jenkins.BackendJobCreateParam{
			EnvName: envInstance.Name,
			DockerLabel: "",
			Git: envInstance.BackendGit,
			JobName: envInstance.BackendJobName,
			ServiceName: envInstance.BackendServiceName,
			CeleryBeatServiceName: envInstance.CeleryBeatServiceName,
			CeleryWorkerServiceName: envInstance.CeleryWorkerServiceName,
			BackendStaticVolume: envInstance.BackendStaticVolume,
			BackendMediaVolume: envInstance.BackendMediaVolume,
			DockerNetwork: envInstance.DockerNetwork,
			DockerRepository: setting.DockerRepo,
			DockerManagerHost: setting.DockerManagerHost,
			DockerManagerUser: setting.DockerManagerUser,
			DockerManagerPort: setting.DockerManagerPort,
			JobScriptTemplateFile: setting.JenkinsBackendScriptTemplate,
			JobConfigTemplateFile: setting.JenkinsBackendJobTemplate,
		}

		applicationRestartJobCreateParam := jenkins.ApplicationRestartJobCreateParam {
			EnvName: envInstance.Name,
			JobName: envInstance.ApplicationRestartJobName,
			BackendServiceName: envInstance.BackendServiceName,
			CeleryBeatServiceName: envInstance.CeleryBeatServiceName,
			CeleryWorkerServiceName: envInstance.CeleryWorkerServiceName,
			DockerManagerHost: setting.DockerManagerHost,
			DockerManagerUser: setting.DockerManagerUser,
			DockerManagerPort: setting.DockerManagerPort,
			JobScriptTemplateFile: setting.JenkinsApplicationRestartScriptTemplate,
			JobConfigTemplateFile: setting.JenkinsApplicationRestartJobTemplate,

		}

		err = jenkins.GenerateJenkinsJobs(
			customerJobCreateParam,
			backendJobCreateParam,
			adminJobCreateParam,
			// mobileJobCreateParam,
			dBJobCreateParam,
			applicationRestartJobCreateParam,
			)
		if err != nil {
			err := models.WriteCreateState(envName, err.Error(), "JenkinsCreateState")
			if err != nil {
				log.Println(err)
				return
			}
			// write db
		} else {
			err := models.WriteCreateState(envName, "ok", "JenkinsCreateState")
			if err != nil {
				log.Println(err)
				return
			}
			//write db
		}
	}()

	return nil
}